package docsync;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class FolderSync {
	public interface Action {
		public void perform() throws Exception;
	}

	public class Create implements Action {
		private FolderInterface destfolder;
		private FileInterface srcfile;

		public Create(FolderInterface dst, FileInterface file) {
			this.destfolder = dst;
			this.srcfile = file;
		}

		public void perform() throws Exception {
			FileInterface dstfile = destfolder.create(srcfile.getFilename());
			dstfile.write(srcfile.read(), srcfile.length());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			Formatter f = new Formatter(sb);
			f.format("Create %s in %s.", srcfile.getFilename(), destfolder
					.toString());
			return sb.toString();
		}
	}

	public class DuplicateName implements Action {
		String filename;

		public DuplicateName(String filename) {
			this.filename = filename;
		}

		public void perform() {
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			Formatter f = new Formatter(sb);
			f.format("Duplicated name '%s' cannot be sync", filename);
			return sb.toString();
		}
	}

	public class Replace implements Action {
		FileInterface oldfile, newfile;
		FolderInterface oldfolder, newfolder;

		public Replace(FolderInterface oldfolder, FolderInterface newfolder,
				FileInterface oldfile, FileInterface newfile) {
			this.oldfolder = oldfolder;
			this.newfolder = newfolder;
			this.oldfile = oldfile;
			this.newfile = newfile;
		}

		public void perform() throws Exception {
			this.oldfile.write(this.newfile.read(), this.newfile.length());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			Formatter f = new Formatter(sb);
			f.format("Replace %s on %s with the version on %s", this.oldfile.getFilename(),
					oldfolder, newfolder);
			return sb.toString();
		}
	}

	private FolderInterface _folderA;
	private FolderInterface _folderB;

	public FolderSync(FolderInterface folder1, FolderInterface folder2) {
		_folderA = folder1;
		_folderB = folder2;
	}

	/**
	 * 
	 * @param fa
	 * @param fb
	 * @return the most recent file; or, NULL is they are the same.
	 */
	public FileInterface mostRecent(FileInterface fa, FileInterface fb) {
		if (fa.lastModify() != fb.lastModify()) {
			if (fa.lastModify() > fb.lastModify()) {
				return fa;
			} else {
				return fb;
			}
		} else
			return null;
	}

	public List<Action> sync() throws Exception {
		ArrayList<Action> todos = new ArrayList<Action>();
		appendToList(todos, _folderA, _folderB);
		return todos;
	}

	public List<Action> appendToList(List<Action> todos, FolderInterface dst,
			FolderInterface src) throws Exception {
		FileInterface[] srcfiles = src.getFiles();
		FileInterface[] dstfiles = dst.getFiles();
		Map<String, FileInterface> srcmap = new TreeMap<String, FileInterface>();
		Map<String, FileInterface> dstmap = new TreeMap<String, FileInterface>();
		filesToMap(srcfiles, srcmap);
		filesToMap(dstfiles, dstmap);

		Set<String> srcset = new TreeSet<String>();
		Set<String> dstset = new TreeSet<String>();
		createUniqueSet(todos, srcfiles, srcset);
		createUniqueSet(todos, dstfiles, dstset);

		Set<String> newforsrc = difference(srcset, dstset);
		Set<String> newfordst = difference(dstset, srcset);

		Set<String> intersect = intersection(srcset, dstset);

		for (String s : intersect) {// replace existing files with the most
			// recent revision
			FileInterface fs = srcmap.get(s);
			FileInterface fd = dstmap.get(s);
			FileInterface recent = mostRecent(fs, fd);
			if (recent != null) {
				if (recent == fs) {
					// System.out.println(recent.lastModify()+" fd"+
					// fd.lastModify());
					todos.add(new Replace(dst, src, fd, fs));
				} else {
					// System.out.println(recent.lastModify()+" fs"+
					// fs.lastModify());
					todos.add(new Replace(src, dst, fs, fd));
				}
			}
		}

		for (String s : newforsrc) {
			todos.add(new Create(src, dstmap.get(s)));
		}

		for (String s : newfordst) {
			todos.add(new Create(dst, srcmap.get(s)));
		}

		return todos;
	}

	private void filesToMap(FileInterface[] srcfiles,
			Map<String, FileInterface> srcmap) {
		for (FileInterface f : srcfiles) {
			srcmap.put(f.getFilename(), f);
		}
	}

	private Set<String> difference(Set<String> srcset, Set<String> dstset) {
		Set<String> newforsrc = new TreeSet<String>(dstset);
		newforsrc.removeAll(srcset);
		return newforsrc;
	}

	private Set<String> intersection(Set<String> srcset, Set<String> dstset) {
		Set<String> intersect = new TreeSet<String>();
		for (String s : srcset) {
			if (dstset.contains(s)) {
				intersect.add(s);
			}
		}
		return intersect;
	}

	private void createUniqueSet(List<Action> todos, FileInterface[] files,
			Set<String> set) {
		for (FileInterface f : files) {
			if (set.contains(f.getFilename())) {
				todos.add(new DuplicateName(f.getFilename()));
				set.remove(f.getFilename());
			}
			set.add(f.getFilename());
		}
	}

}
